from typing import Union, List

import minimalmodbus
import serial.tools.list_ports


class ModbusRTUClient:
    """
    Modbus-RTU 客户端封装类，支持常用功能码操作
    方法命名格式: read_xxx / write_xxx (功能码解耦)
    """

    def __init__(
            self,
            port: str,
            slave_address: int,
            baudrate: int = 115200,
            bytesize: int = 8,
            parity: str = 'N',
            stopbits: int = 1,
            timeout: float = 0.5
    ):
        """
        初始化 Modbus 客户端
        :param port: 串口路径 (如 '/dev/ttyUSB0' 或 'COM1')
        :param slave_address: 从站地址
        :param baudrate: 波特率
        :param bytesize: 数据位
        :param parity: 校验位 (N-无校验, E-偶校验, O-奇校验)
        :param stopbits: 停止位
        :param timeout: 超时时间 (秒)
        """
        self.instrument = minimalmodbus.Instrument(port, slave_address)
        self.instrument.serial.baudrate = baudrate
        self.instrument.serial.bytesize = bytesize
        self.instrument.serial.parity = parity
        self.instrument.serial.stopbits = stopbits
        self.instrument.serial.timeout = timeout

    @classmethod
    def detect_serial_ports(cls):
        return list(serial.tools.list_ports.comports())

    def _handle_exception(self, e: Exception) -> dict:
        """统一处理异常并返回错误信息"""
        return {
            'success': False,
            'data': None,
            'message': f"Modbus通信失败: {str(e)}"
        }

    def read_coils(self, start_address: int, count: int = 1) -> dict:
        """
        读取线圈状态 (功能码 0x01)
        :param start_address: 起始地址
        :param count: 读取数量
        :return: {success: bool, data: List[bool], message: str}
        """
        try:
            values = self.instrument.read_bits(start_address, count, functioncode=1)
            return {'success': True, 'data': values, 'message': ''}
        except Exception as e:
            return self._handle_exception(e)

    def read_input_registers(self, start_address: int, count: int = 1) -> dict:
        """
        读取输入寄存器 (功能码 0x03)
        :param start_address: 起始地址
        :param count: 读取数量
        :return: {success: bool, data: List[int], message: str}
        """
        try:
            values = self.instrument.read_registers(start_address, count, functioncode=3)
            return {'success': True, 'data': values, 'message': ''}
        except Exception as e:
            return self._handle_exception(e)

    def write_single_coil(self, address: int, value: bool) -> dict:
        """
        写入单个线圈 (功能码 0x05)
        :param address: 线圈地址
        :param value: 写入值 (True/False)
        :return: {success: bool, data: None, message: str}
        """
        try:
            self.instrument.write_bit(address, int(value), functioncode=5)
            return {'success': True, 'data': None, 'message': ''}
        except Exception as e:
            return self._handle_exception(e)

    def write_multiple_registers(
            self,
            start_address: int,
            values: Union[List[int], int]
    ) -> dict:
        """
        写入多个保持寄存器 (功能码 0x10)
        :param start_address: 起始地址
        :param values: 写入值 (单个int或列表)
        :return: {success: bool, data: None, message: str}
        """
        try:
            if isinstance(values, int):
                values = [values]
            self.instrument.write_registers(start_address, values)
            return {'success': True, 'data': None, 'message': ''}
        except Exception as e:
            return self._handle_exception(e)

    def close(self):
        """关闭串口连接"""
        self.instrument.serial.close()





if __name__ == "__main__":
    # 初始化客户端 (从站地址1，串口设备根据实际情况修改)
    client = ModbusRTUClient(port='COM13', baudrate=115200, slave_address=1)

    # 示例1: 读取输入寄存器 (地址0，读取2个寄存器)
    result = client.read_input_registers(0x51, 76)
    # result = client.read_coils(1, 50)
    if result['success']:
        print(f"读取输入寄存器成功: {result['data']}")
    else:
        print(f"读取失败: {result['message']}")

    # # 示例2: 写入多个保持寄存器 (地址10，写入[100, 200])
    # result = client.write_multiple_registers(10, [100, 200])
    # print("写入操作成功" if result['success'] else f"写入失败: {result['message']}")

    # 关闭连接
    client.close()
